package seteam12;

import java.util.Arrays;


/**
 * Solution helper class for RandomizedSolver. Represents a solution snapshot
 * that may or may not be correct.
 * 
 * @author Dhyan Blum
 * 
 */
class Solution {
    private int[][] matrix = null;
    private int overallErrors = Integer.MAX_VALUE;

    /**
     * @param matrix
     * @param overallErrors
     */
    public Solution(final int[][] matrix) {
	this.setMatrix(matrix);
    }

    public final void setMatrix(final int[][] matrix) {
	this.matrix = matrix;
    }

    public int[][] getMatrix() {
	return this.matrix;
    }

    public int getOverallErrors() {
	return overallErrors;
    }

    /**
     * Checks that each digit from 1-9 appears exactly once in the given array.
     * 
     * @param digits
     *            Must only contain digits from 1-9 (0 will be ignored)
     * @return Number of digits that appear more or less than once
     */
    private static int countErrors(final int[] digits) {
	int errors = 0;
	int[] occurrences = new int[10];
	// Count occurrences
	for (int i = 0; i < digits.length; i++) {
	    occurrences[digits[i]]++;
	}
	// Check digits (ignore 0)
	for (int i = 1; i < occurrences.length; i++) {
	    if (occurrences[i] != 1) {
		errors++;
	    }
	}
	return errors;
    }

    /**
     * Counts the occurrences of digit in digits
     * 
     * @param digits
     *            Must only contain digits from 1-9 (0 will be ignored)
     * @return Number of occurrences of digit in digits
     */
    private static int countDigit(final int[] digits, final int digit) {
	int occurrences = 0;
	for (int i = 0; i < digits.length; i++) {
	    if (digits[i] == digit) {
		occurrences++;
	    }
	}
	return occurrences;
    }

    /**
     * @param matrix
     * @return Number of errors in all matrix rows
     */
    private static int countRowErrors(final int[][] matrix) {
	int errors = 0;
	for (int i = 0; i < matrix.length; i++) {
	    final int[] row = matrix[i];
	    errors += countErrors(row);
	}
	return errors;
    }

    /**
     * @param matrix
     * @return Number of errors in all matrix columns
     */
    private static int countColumnErrors(final int[][] matrix) {
	int errors = 0;
	for (int i = 0; i < matrix[0].length; i++) {
	    final int[] col = getColumn(i, matrix);
	    errors += countErrors(col);
	}
	return errors;
    }

    /**
     * @param matrix
     * @return Number of errors in all matrix boxes
     */
    private static int countBoxErrors(final int[][] matrix) {
	int errors = 0;
	for (int i = 0; i < matrix[0].length; i++) {
	    final int[] box = getSubgrid(i, matrix);
	    errors += countErrors(box);
	}
	return errors;
    }

    /**
     * @param matrix
     * @return Number of errors in the given matrix
     */
    public static int countOverallErrors(final int[][] matrix) {
	return countColumnErrors(matrix) + countRowErrors(matrix)
		+ countBoxErrors(matrix);
    }

    /**
     * Recalculate the number of errors according to the given matrix
     */
    public final void updateErrors() {
	this.overallErrors = countOverallErrors(matrix);
    }

    /**
     * @param matrix
     * @param index
     *            From 0 to matrix.length-1
     * @return The column with the given index
     */
    private static int[] getColumn(final int index, final int[][] matrix) {
	int[] column = new int[matrix[0].length];
	for (int i = 0; i < column.length; i++) {
	    column[i] = matrix[i][index];
	}
	return column;
    }

    /**
     * @param matrix
     * @param index
     *            Sub-grid index from 0 to matrix.length-1 (it is assumed that
     *            sub-grids are numbered from top left to bottom right)
     * @return The sub-grid with the given index as single array
     */
    private static int[] getSubgrid(final int index, final int[][] matrix) {
	int[] subgrid = new int[matrix[0].length];
	final int startRow = (index / 3) * 3;
	int resultPos = 0;
	for (int i = startRow; i < startRow + 3; i++) {
	    final int startColumn = (index % 3) * 3;
	    for (int j = startColumn; j < startColumn + 3; j++) {
		subgrid[resultPos] = matrix[i][j];
		resultPos++;
	    }
	}
	return subgrid;
    }

    /**
     * Swap two cells of the matrix
     * 
     * @param cell1
     * @param cell2
     */
    public void swapCells(final PrioCell cell1, final PrioCell cell2) {
	cell1.value = matrix[cell1.line][cell1.column];
	matrix[cell1.line][cell1.column] = matrix[cell2.line][cell2.column];
	matrix[cell2.line][cell2.column] = cell1.value;
    }

    /**
     * @param cell
     * @param matrix
     * @return true if the digit in the given cell appears more than once in its
     *         row, column or box of the given matrix
     */
    public static boolean isDoubleDigit(final PrioCell cell,
	    final int[][] matrix) {
	final int digit = matrix[cell.line][cell.column];
	final int[] row = matrix[cell.line];
	final int[] col = getColumn(cell.column, matrix);
	final int[] box = getSubgrid(getBoxIndex(cell.line, cell.column),
		matrix);
	if (countDigit(row, digit) > 1 || countDigit(col, digit) > 1
		|| countDigit(box, digit) > 1) {
	    return true;
	}
	return false;
    }

    private static int getBoxIndex(final int row, final int col) {
	return 3 * (row / 3) + (col / 3);
    }

    /**
     * @param matrix1
     * @param matrix2
     * @return true if matrix1 and matrix2 are equal, false otherwise
     */
    private static boolean areEqual(final int[][] matrix1, final int[][] matrix2) {
	if (matrix1.length != matrix2.length
		|| matrix1[0].length != matrix2[0].length) {
	    return false;
	}
	for (int i = 0; i < matrix1.length; i++) {
	    for (int j = 0; j < matrix1[0].length; j++) {
		if (matrix1[i][j] != matrix2[i][j]) {
		    return false;
		}
	    }
	}
	return true;
    }

    @Override
    public boolean equals(final Object obj) {
	// PMD: "No need to check for null before an instanceof
	if (obj instanceof Solution) {
	    final Solution solution = (Solution) obj;
	    return areEqual(this.matrix, solution.matrix);
	}
	return false;
    }

    public void print() {
	for (int i = 0; i < matrix.length; i++) {
	    for (int j = 0; j < matrix[0].length; j++) {
		System.out.print(matrix[i][j]);
		System.out.print(" ");
	    }
	    System.out.println();
	}
    }

    /**
     * @return Deep copy of this object
     */
    public Solution copy() {
	int[][] matrixCopy = new int[matrix.length][];
	for (int i = 0; i < matrix.length; i++) {
	    matrixCopy[i] = Arrays.copyOf(matrix[i], matrix.length);
	}
	final Solution copy = new Solution(matrixCopy);
	copy.updateErrors();
	return copy;
    }
}